#include "il2cpp-config.h"
#include <cassert>
#include "object-internals.h"
#include "class-internals.h"
#include "icalls/mscorlib/System/Enum.h"
#include "vm/Class.h"
#include "vm/Object.h"
#include "vm/Exception.h"
#include "vm/Reflection.h"

using namespace il2cpp::vm;

namespace il2cpp
{
namespace icalls
{
namespace mscorlib
{
namespace System
{

Il2CppObject * Enum::get_value(Il2CppObject *__this)
{
	if (!__this)
		return NULL;

	assert (__this->klass->enumtype);
	
	Il2CppClass* enumClass = Class::FromIl2CppType (Class::GetEnumBaseType (__this->klass));
	Il2CppObject* res = Object::New (enumClass);
	void* dst = (char *)res + sizeof (Il2CppObject);
	void* src = (char *)__this + sizeof (Il2CppObject);
	int32_t size = Class::GetValueSize (enumClass, NULL);

	memcpy (dst, src, size);

	return res;

	return il2cpp::vm::Object::Box (__this->klass->element_class, (char *)__this + sizeof (Il2CppObject));
}

int Enum::compare_value_to(Il2CppObject * __this, Il2CppObject * other)
{	
	void* tdata = (char *)__this + sizeof (Il2CppObject);
	void* odata = (char *)other + sizeof (Il2CppObject);
	const Il2CppType *basetype = Class::GetEnumBaseType (Object::GetClass (__this));
	assert (basetype);

#define COMPARE_ENUM_VALUES(ENUM_TYPE) do { \
		ENUM_TYPE me = *((ENUM_TYPE*)tdata); \
		ENUM_TYPE other = *((ENUM_TYPE*)odata); \
		if (me == other) \
			return 0; \
		return me > other ? 1 : -1; \
	} while (0)

#define COMPARE_ENUM_VALUES_RANGE(ENUM_TYPE) do { \
		ENUM_TYPE me = *((ENUM_TYPE*)tdata); \
		ENUM_TYPE other = *((ENUM_TYPE*)odata); \
		if (me == other) \
			return 0; \
		return me - other; \
	} while (0)

	switch (basetype->type) {
		case IL2CPP_TYPE_U1:
			COMPARE_ENUM_VALUES (uint8_t);
		case IL2CPP_TYPE_I1:
			COMPARE_ENUM_VALUES (int8_t);
		case IL2CPP_TYPE_CHAR:
			COMPARE_ENUM_VALUES_RANGE (Il2CppChar);
		case IL2CPP_TYPE_U2:
			COMPARE_ENUM_VALUES_RANGE (uint16_t);
		case IL2CPP_TYPE_I2:
			COMPARE_ENUM_VALUES (int16_t);
		case IL2CPP_TYPE_U4:
			COMPARE_ENUM_VALUES (uint32_t);
		case IL2CPP_TYPE_I4:
			COMPARE_ENUM_VALUES (int32_t);
		case IL2CPP_TYPE_U8:
			COMPARE_ENUM_VALUES (uint64_t);
		case IL2CPP_TYPE_I8:
			COMPARE_ENUM_VALUES (int64_t);
		default:
			assert(false && "Implement type 0x%02x in compare_value_to");
	}

#undef COMPARE_ENUM_VALUES_RANGE
#undef COMPARE_ENUM_VALUES
	return 0;
}

int32_t Enum::get_hashcode(Il2CppObject * __this)
{
	void* data = (char *)__this + sizeof (Il2CppObject);
	Il2CppClass *basetype = __this->klass->element_class;
	assert (basetype);

	if (basetype == il2cpp_defaults.sbyte_class)
		return *((int8_t*)data);
	if (basetype == il2cpp_defaults.byte_class)
		return *((uint8_t*)data);
	if (basetype == il2cpp_defaults.char_class)
		return *((Il2CppChar*)data);
	if (basetype == il2cpp_defaults.uint16_class)
		return *((uint16_t*)data);
	if (basetype == il2cpp_defaults.int16_class)
		return *((uint16_t*)data);
	if (basetype == il2cpp_defaults.uint32_class)
		return *((uint32_t*)data);
	if (basetype == il2cpp_defaults.int32_class)
		return *((int32_t*)data);
	if (basetype == il2cpp_defaults.uint64_class || basetype == il2cpp_defaults.int64_class)
	{
		int64_t value = *((int64_t*)data);
		return (int32_t)(value & 0xffffffff) ^ (int32_t)(value >> 32);
	}

	assert(0 && "System_Enum_get_hashcode_icall");
	return 0;
}

static uint64_t
read_enum_value (char *mem, Il2CppClass* type)
{
	if (type == il2cpp_defaults.byte_class)
		return *(int8_t*)mem;
	if (type == il2cpp_defaults.sbyte_class)
		return *(uint8_t*)mem;
	if (type == il2cpp_defaults.uint16_class)
		return *(uint16_t*)mem;
	if (type == il2cpp_defaults.int16_class)
		return *(int16_t*)mem;
	if (type == il2cpp_defaults.uint32_class)
		return *(uint32_t*)mem;
	if (type == il2cpp_defaults.int32_class)
		return *(int32_t*)mem;
	if (type == il2cpp_defaults.uint64_class)
		return *(uint64_t*)mem;
	if (type == il2cpp_defaults.int64_class)
		return *(int64_t*)mem;

	assert(0);

	return 0;
}

static void
write_enum_value (char *mem, Il2CppClass* type, uint64_t value)
{
	if (type == il2cpp_defaults.byte_class || type == il2cpp_defaults.sbyte_class)
	{
		uint8_t *p = (uint8_t*)mem;
		*p = (uint8_t) value;
	}
	else if (type == il2cpp_defaults.uint16_class || type == il2cpp_defaults.int16_class)
	{
		uint16_t *p = (uint16_t*)mem;
		*p = (uint16_t) value;
	}
	else if (type == il2cpp_defaults.uint32_class || type == il2cpp_defaults.int32_class)
	{
		uint32_t *p = (uint32_t*)mem;
		*p = (uint32_t) value;
	}
	else if (type == il2cpp_defaults.uint64_class || type == il2cpp_defaults.int64_class)
	{
		uint64_t *p = (uint64_t*)mem;
		*p = value;
	}
	else
	{
		assert(0);
	}

	return;
}

Il2CppObject * Enum::ToObject (Il2CppReflectionType * enumType,Il2CppObject * value)
{
	//MonoDomain *domain; 
	Il2CppClass *enumc, *objc;
	Il2CppObject *res;
	Il2CppClass *etype;
	uint64_t val;

	IL2CPP_CHECK_ARG_NULL (enumType);
	IL2CPP_CHECK_ARG_NULL (value);

	//domain = mono_object_domain (enumType); 
	enumc = Class::FromIl2CppType (enumType->type);
	objc = Object::GetClass (value);

	//if (!enumc->enumtype)
	//	mono_raise_exception (mono_get_exception_argument ("enumType", "Type provided must be an Enum."));
	//if (!((objc->enumtype) || (objc->byval_arg.type >= MONO_TYPE_I1 && objc->byval_arg.type <= MONO_TYPE_U8)))
	//	mono_raise_exception (mono_get_exception_argument ("value", "The value passed in must be an enum base or an underlying type for an enum, such as an Int32."));

	etype = Class::GetElementClass (enumc);
	if (!etype)
		/* MS throws this for typebuilders */
		il2cpp::vm::Exception::Raise (il2cpp::vm::Exception::GetArgumentException ("Type must be a type provided by the runtime.", "enumType"));

	res = (Il2CppObject*)il2cpp::vm::Object::New (enumc);
	//val = read_enum_value ((char *)value + sizeof (mscorlib_System_Object), objc->enumtype? mono_class_enum_basetype (objc)->type: objc->byval_arg.type);
	val = read_enum_value ((char *)value + sizeof (Il2CppObject), objc->enumtype ? objc->element_class : objc);
	write_enum_value ((char *)res + sizeof (Il2CppObject), etype, val);

	return res;
}

Il2CppReflectionType * Enum::get_underlying_type(Il2CppReflectionType *type)
{
	const Il2CppType *etype;

	etype = Class::GetEnumBaseType (Class::FromIl2CppType (type->type));
	if (!etype)
		/* MS throws this for typebuilders */
		il2cpp::vm::Exception::Raise (il2cpp::vm::Exception::GetArgumentException ("Type must be a type provided by the runtime.", "enumType"));

	return il2cpp::vm::Reflection::GetTypeObject (etype);
}

} /* namespace System */
} /* namespace mscorlib */
} /* namespace icalls */
} /* namespace il2cpp */
